package hk.ust.comp3111.ustnutrition.data;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;

import com.couchbase.lite.CouchbaseLiteException;
import com.couchbase.lite.LiveQuery;
import com.couchbase.lite.LiveQuery.ChangeEvent;
import com.couchbase.lite.LiveQuery.ChangeListener;
import com.couchbase.lite.Query;
import com.couchbase.lite.QueryEnumerator;

public class DataTableQuery<T extends DataRow> implements Collection<T>, ChangeListener, com.couchbase.lite.Database.ChangeListener {
	public DataTableQuery(Query query, DataTable<T> parent) {
		this.parent = parent;
		setQuery(query);
	}
	
	protected DataTableQuery() {
	}
	
	protected void setQuery(Query query) {
		if(this.query != null) {
			if(this.query.getView() == null)
				parent.database.removeChangeListener(this);
			else
				((LiveQuery)this.query).removeChangeListener(this);
		}
		this.query = null;
		if(query != null) {
			if(query.getView() == null) {
				parent.database.addChangeListener(this);
				this.query = query;
			} else {
				this.query = query.toLiveQuery();
				((LiveQuery)this.query).addChangeListener(this);
			}
		}
	}
	
	public DataTableQuery<T> setKeys(List<Object> keys) {
		List<Object> akeys = new ArrayList<Object>();
		akeys.add(keys);
		
		query.setKeys(akeys);
		
		return this;
	}
	
	public DataTableQuery<T> setStart(List<Object> keys) {
		query.setStartKey(keys);
		return this;
	}
	
	public DataTableQuery<T> setEnd(List<Object> keys) {
		query.setEndKey(keys);
		return this;
	}
	
	public DataTableQuery<T> setKey(Object key) {
		List<Object> keys = new ArrayList<Object>();
		keys.add(key);
		
		List<Object> akeys = new ArrayList<Object>();
		akeys.add(keys);
		
		query.setKeys(akeys);
		return this;
	}
	
	public DataTableQuery<T> setStart(Object key) {
		query.setStartKey(key);
		return this;
	}
	
	public DataTableQuery<T> setEnd(Object key) {
		query.setEndKey(key);
		return this;
	}
	
	protected DataTable<T> parent;
	Query query;
	
	@Override
	public boolean add(T object) {
		throw new UnsupportedOperationException();
	}

	@Override
	public boolean addAll(Collection<? extends T> arg0) {
		throw new UnsupportedOperationException();
	}

	@Override
	public void clear() {
		throw new UnsupportedOperationException();
	}

	@Override
	public boolean contains(Object object) {
		throw new UnsupportedOperationException();
	}

	@Override
	public boolean containsAll(Collection<?> arg0) {
		throw new UnsupportedOperationException();
	}

	@Override
	public boolean isEmpty() {
		throw new UnsupportedOperationException();
	}

	@Override
	public boolean remove(Object object) {
		throw new UnsupportedOperationException();
	}

	@Override
	public boolean removeAll(Collection<?> arg0) {
		throw new UnsupportedOperationException();
	}

	@Override
	public boolean retainAll(Collection<?> arg0) {
		throw new UnsupportedOperationException();
	}

	@Override
	public int size() {
		throw new UnsupportedOperationException();
	}
	

	public class RowFilter {
		public boolean filter(T item) {
			return true;
		}
	}

	class MyIterator implements Iterator<T> {
		T item;
		QueryEnumerator e;
		int count;
		RowFilter filter;

		public MyIterator(Query query, RowFilter filter, int count)
				throws CouchbaseLiteException {
			query.setLimit(filter == null && count != -1 ? count : 0x7FFFFFFF);
			e = query.run();
			this.count = count;
			this.filter = filter;
			moveNext();
		}

		private void moveNext() {
			if (count != -1) {
				if(count == 0) {
					item = null;
					return;
				}
				count--;
			}

			if (filter == null) {
				if (e.hasNext())
					item = parent.get(e.next().getDocumentId());
				else
					item = null;
			} else {
				item = null;
				while(e.hasNext() && item == null) {
					item = parent.get(e.next().getDocumentId());
					if(!filter.filter(item))
						item = null;
				}
			}
		}

		@Override
		public boolean hasNext() {
			return item != null;
		}

		@Override
		public T next() {
			T last = item;
			moveNext();
			return last;
		}

		@Override
		public void remove() {
			throw new UnsupportedOperationException();
		}
	}

	@Override
	public Iterator<T> iterator() {
		return take(-1);
	}

	public Iterator<T> take(int count) {
		return take(null, count);
	}

	public Iterator<T> take(RowFilter filter, int count) {
		MyIterator it;
		try {
			it = new MyIterator(query, filter,
					count);
		} catch (CouchbaseLiteException e) {
			e.printStackTrace();
			throw new RuntimeException(e);
		}
		return it;
	}

	@Override
	public Object[] toArray() {
		Object[] obj = new Object[size()];
		int c = 0;
		for (T item : this)
			obj[c++] = item;
		return obj;
	}

	@SuppressWarnings({ "unchecked", "hiding" })
	@Override
	public <T> T[] toArray(T[] array) {
		int c = 0;
		for (Object item : this)
			array[c++] = (T) item;
		return array;
	}

	@Override
	public void changed(ChangeEvent arg0) {
		DataTableListener.ChangeEvent<T> event = new DataTableListener.ChangeEvent<T>(parent, arg0);
		for(DataTableListener<T> listener : listeners)
			listener.changed(event);
	}
	
	protected List<DataTableListener<T>> listeners = new ArrayList<DataTableListener<T>>();
	
	public void addChangeListener(DataTableListener<T> listener) {
		listeners.add(listener);
	}
	
	public void removeChangeListener(DataTableListener<T> listener) {
		listeners.remove(listener);
	}

	@Override
	public void changed(com.couchbase.lite.Database.ChangeEvent arg0) {
		DataTableListener.ChangeEvent<T> event = new DataTableListener.ChangeEvent<T>(parent, arg0);
		for(DataTableListener<T> listener : listeners)
			listener.changed(event);
	}
}
